﻿// -----------------------------------------------------------------------
// <copyright file="FieldSet.cs" company="Microsoft">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace Proto.Fields
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    public partial struct FieldSet
    {
        public enum BitwiseCheckTypes
        {
            Any,
            All,
        }

        private const int m_NUM_INTS = 3;

        private int m_Data1;
        private int m_Data2;
        private int m_Data3;

        private int this[int index]
        {
            get
            {
                switch (index)
                {
                    case (0):
                        return m_Data1;
                    case (1):
                        return m_Data2;
                    case (2):
                        return m_Data3;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
            set
            {
                switch (index)
                {
                    case (0):
                        m_Data1 = value;
                        break;
                    case (1):
                        m_Data2 = value;
                        break;
                    case (2):
                        m_Data3 = value;
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }

        private FieldID[] m_Fields;

        public FieldSet(params FieldID[] fields)
        {
            m_Fields = fields;
            m_Data1 = 0;
            m_Data2 = 0;
            m_Data3 = 0;
            for (int i = 0; i < fields.Length; i++)
            {
                int index = (int)fields[i];
                this[index / 32] |= 1 << (index % 32);
            }
        }


        public bool IsEmpty()
        {
            for (int i = 0; i < m_NUM_INTS; i++)
            {
                if (this[i] != 0) return false;
            }
            return true;
        }

        public void Add(FieldID field)
        {
            int index = (int)field;

            if ( Contains( field ) ) return;

            this[index / 32] |= 1 << (index % 32);

            if (m_Fields == null)
            {
                List<FieldID> list = new List<FieldID>();
                for (int i = 0; i / 32 < m_NUM_INTS; i++)
                {
                    FieldID inner_field = (FieldID)i;
                    if (Contains(inner_field))
                    {
                        list.Add(inner_field);
                    }
                }
                m_Fields = list.ToArray();
            }
            else
            {
                // not thread safe... i can use the other method for this
                FieldID[] new_array = new FieldID[m_Fields.Length + 1];
                m_Fields.CopyTo(new_array, 0);
                new_array[m_Fields.Length] = field;
                m_Fields = new_array;
            }
        }

        public void Add(FieldSet field)
        {

            for (int i = 0; i < m_NUM_INTS; i++)
            {
                // if this is outside the loop i am getting m_Data is null exceptions
                // not infrequently. I don't really want to lock and have changed
                // it to volatile. Locking here would be ridiculous.

                this[i] = (this[i] | field[i]);
            }
            m_Fields = null;
        }

        public IEnumerable<FieldID> Fields
        {
            get
            {
                if (m_Fields == null)
                {
                    List<FieldID> list = new List<FieldID>();
                    for (int i = 0; i / 32 < m_NUM_INTS; i++)
                    {
                        FieldID field = (FieldID)i;
                        if (Contains(field))
                        {
                            list.Add(field);
                        }
                    }
                    m_Fields = list.ToArray();
                }
                for (int i = 0; i < m_Fields.Length; i++)
                {
                    yield return m_Fields[i];
                }
            }
        }

        public bool Contains(FieldID field)
        {
            int index = (int)field;
            return (this[index / 32] & 1 << (index % 32)) > 0;
        }

        public bool Contains(FieldID[] fields, BitwiseCheckTypes condition)
        {
            for (int i = 0; i < fields.Length; i++)
            {
                if (condition == BitwiseCheckTypes.All && Contains(fields[i]) == false)
                {
                    return false;
                }
                else if (condition == BitwiseCheckTypes.Any && Contains(fields[i]) )
                {
                    return true;
                }
            }
            return condition == BitwiseCheckTypes.All;
        }

        public bool Contains(FieldSet composite, BitwiseCheckTypes condition)
        {

            foreach (FieldID field in this.Fields)
            {
                if (condition == BitwiseCheckTypes.All && Contains(field) == false)
                {
                    return false;
                }
                else if (condition == BitwiseCheckTypes.Any && Contains(field) )
                {
                    return true;
                }
            }
            return condition == BitwiseCheckTypes.All;
        }

        public override bool Equals(object obj)
        {
            if (obj is FieldSet)
            {
                return Equals((FieldSet)obj);
            }
            return false;
        }

        public bool Equals(FieldSet other)
        {

            FieldSet rhs = other;
            // if null then equality equals nullness.

            for (int i = 0; i < m_NUM_INTS; i++)
            {
                if (this[i] != rhs[i])
                {
                    return false;
                }
            }
            return true;
        }

        public static FieldSet operator &(FieldSet lhs, FieldSet rhs)
        {
            for (int i = 0; i < m_NUM_INTS; i++)
            {
                lhs[i] = lhs[i] & rhs[i];
            }
            return lhs;
        }

        public static FieldSet operator |(FieldSet lhs, FieldSet rhs)
        {
            for (int i = 0; i < m_NUM_INTS; i++)
            {
                lhs[i] = lhs[i] | rhs[i];
            }
            return lhs;
        }
        public static FieldSet operator |( FieldSet lhs, FieldID rhs)
        {
            lhs.Add( rhs );
            return lhs;
        }


        public static bool operator ==(FieldSet lhs, FieldSet rhs)
        {
            return lhs.Equals(rhs);
        }

        public static bool operator !=(FieldSet lhs, FieldSet rhs)
        {
            return !(lhs == rhs);
        }

        public static explicit operator FieldSet(FieldID field)
        {
            return new FieldSet(field);
        }


        public override int GetHashCode()
        {
            return
                m_Data1 & (m_Data2 << 8) & m_Data3 << 8;
        }
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            bool is_first = true;
            foreach (FieldID field in this.Fields)
            {
                if (!is_first)
                {
                    sb.Append(',');
                }
                else
                {
                    is_first = false;
                }
                sb.Append(Enum.GetName(typeof(FieldID), field));
            }
            return sb.ToString();
        }
    }

}
